﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System;

using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using SHDocVw;
using WatiN.Core;
using mshtml;

namespace WindowsFormsApplication4
{
    public static class WatinScreenshotSaver
    {
        //public static Bitmap bm;

        public static void SaveBrowserWindowScreenshotWithHighlight
            (Element element, string screenshotName)
        {
            HighlightElement(element, true);

            SaveBrowserWindowScreenshot(element, screenshotName);

            HighlightElement(element, false);
        }

        public static void SaveBrowserWindowScreenshotWithHighlight(Element element)
        {
            HighlightElement(element, true);

            SaveBrowserWindowScreenshot(element);

            HighlightElement(element, false);
        }

        public static void SaveBrowserWindowScreenshot(Element element, string screenshotName)
        {
            SaveScreenshot(GetIe(element), screenshotName, SaveBitmapForCallbackArgs);
        }

        public static void SaveBrowserWindowScreenshot(Element element)
        {
            SaveScreenshot(GetIe(element), null, SaveBitmapForCallbackArgs);
        }

        public static void SaveBrowserWindowScreenshot(IE ie, string screenshotName)
        {
            SaveScreenshot(ie, screenshotName, SaveBitmapForCallbackArgs);
        }

        public static void SaveBrowserWindowScreenshot(IE ie)
        {
            SaveScreenshot(ie, null, SaveBitmapForCallbackArgs);
        }

        public static void SaveElementScreenshot(Element element, string screenshotName)
        {
            // TODO: Figure out how to get browser window "chrome" size and not have to go to full screen:
            var iex = (InternetExplorerClass)GetIe(element).InternetExplorer;
            bool fullScreen = iex.FullScreen;
            if (!fullScreen) iex.FullScreen = true;

            ScrollIntoView(element);

            SaveScreenshot(GetIe(element), screenshotName, args =>
                SaveElementBitmapForCallbackArgs(element, args));

            iex.FullScreen = fullScreen;
        }

        public static void SaveElementScreenshot(Element element)
        {
            SaveElementScreenshot(element, null);
        }


       // public static void GetElementBitmap()
        public static void GetElementBitmap(Element element)
        {
            //GetElementBitmapForCallbackArgs(element, null);
            var iex = (InternetExplorerClass)GetIe(element).InternetExplorer;
            bool fullScreen = iex.FullScreen;
            if (!fullScreen) iex.FullScreen = true;

            ScrollIntoView(element);

            SaveScreenshot(GetIe(element), null, args =>
                GetElementBitmapForCallbackArgs(element, args));

            iex.FullScreen = fullScreen;


        }

        private static void SaveScreenshot(IE browser, string screenshotName,
            Action<ScreenshotCallbackArgs> screenshotCallback)
        {
            string fileName = string.Format("{0:000}{1}{2}.jpg",
                ++_screenshotCount,
                (string.IsNullOrEmpty(screenshotName)) ? "" : " ",
                screenshotName);

            string path = Path.Combine(ScreenshotDirectoryName, fileName);

            Console.WriteLine();
            // Gallio HTML-encodes the following display, but I have a utility program to
            // remove the "HTML===" and "===HTML" and un-encode the rest to show images in the Gallio report:
            Console.WriteLine("HTML===<div><b>{0}:</br></b><img src=\"{1}\" /></div>===HTML",
                screenshotName, new Uri(path).AbsoluteUri);

            MakeBrowserWindowTopmost(browser);

            try
            {
                var args = new ScreenshotCallbackArgs
                {
                    InternetExplorerClass = (InternetExplorerClass)browser.InternetExplorer,
                    ScreenshotPath = path
                };

                Thread.Sleep(100);

                screenshotCallback(args);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }

            MakeBrowserWindowBottom(browser);
        }

        public static void HighlightElement(Element element, bool doHighlight)
        {
            if (!element.Exists) return;

            if (string.IsNullOrEmpty(HighlightCssClassName))
            {
                element.Highlight(doHighlight);
                return;
            }

            string jsRef = element.GetJavascriptElementReference();
            if (string.IsNullOrEmpty(jsRef)) return;

            var sb = new StringBuilder("try { ");

            sb.AppendFormat(" {0}.scrollIntoView(false);", jsRef);

            string format = (doHighlight)
                ? "{0}.className += ' {1}'"
                : "{0}.className = {0}.className.replace(' {1}', '')";

            sb.AppendFormat(" " + format + ";", jsRef, HighlightCssClassName);

            sb.Append("} catch(e) {}");

            string script = sb.ToString();

            GetIe(element).RunScript(script);
        }

        public static void ScrollIntoView(Element element)
        {
            string jsRef = element.GetJavascriptElementReference();
            if (string.IsNullOrEmpty(jsRef)) return;

            var sb = new StringBuilder("try { ");
            sb.AppendFormat(" {0}.scrollIntoView(false);", jsRef);
            sb.Append("} catch(e) {}");

            string script = sb.ToString();

            GetIe(element).RunScript(script);
        }

        public static void MakeBrowserWindowTopmost(IE ie)
        {
            ie.BringToFront();
            SetWindowPos(ie.hWnd, HWND_TOPMOST, 0, 0, 0, 0, TOPMOST_FLAGS);
        }

        public static void MakeBrowserWindowBottom(IE ie)
        {
            ie.BringToFront();
            SetWindowPos(ie.hWnd, HWND_BOTTOM, 0, 0, 0, 0, BOTTUM_FLAGS);
        }

        public static string HighlightCssClassName { get; set; }

        private static int _screenshotCount;
        private static string _screenshotDirectoryName;
        public static string ScreenshotDirectoryName
        {
            get
            {
                if (_screenshotDirectoryName == null)
                {
                    var asm = Assembly.GetAssembly(typeof(WatinScreenshotSaver));
                    var uri = new Uri(asm.CodeBase);

                    var fileInfo = new FileInfo(uri.LocalPath);
                    string directoryName = fileInfo.DirectoryName;

                    _screenshotDirectoryName = Path.Combine(
                        directoryName,
                        string.Format("Screenshots_{0:yyyyMMddHHmm}", DateTime.Now));

                    Console.WriteLine("Screenshot folder: {0}", _screenshotDirectoryName);

                    Directory.CreateDirectory(_screenshotDirectoryName);
                }

                return _screenshotDirectoryName;
            }

            set
            {
                _screenshotDirectoryName = value;
                _screenshotCount = 0;
            }
        }

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter,
            int X, int Y, int cx, int cy, uint uFlags);

        private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
        private static readonly IntPtr HWND_BOTTOM = new IntPtr(-2);

        private const UInt32 SWP_NOSIZE = 0x0001;
        private const UInt32 SWP_NOMOVE = 0x0002;
        private const UInt32 TOPMOST_FLAGS = SWP_NOMOVE | SWP_NOSIZE;
        private const UInt32 BOTTUM_FLAGS = SWP_NOMOVE | SWP_NOSIZE;

        private static IE GetIe(Element element)
        {
            if (element == null) return null;

            var container = element.DomContainer;

            while (container as IE == null)
                container = container.DomContainer;

            return (IE)container;
        }

        private static void SaveBitmapForCallbackArgs(ScreenshotCallbackArgs args)
        {
            InternetExplorerClass iex = args.InternetExplorerClass;

            SaveBitmap(args.ScreenshotPath, iex.Left, iex.Top, iex.Width, iex.Height);
        }

        private static void SaveElementBitmapForCallbackArgs(Element element, ScreenshotCallbackArgs args)
        {
            InternetExplorerClass iex = args.InternetExplorerClass;

            Rectangle bounds = GetElementBounds(element);

            SaveBitmap(args.ScreenshotPath,
                iex.Left + bounds.Left,
                iex.Top + bounds.Top,
                bounds.Width,
                bounds.Height);
        }

        private static void GetElementBitmapForCallbackArgs(Element element, ScreenshotCallbackArgs args)
        {
            InternetExplorerClass iex = args.InternetExplorerClass;

            Rectangle bounds = GetElementBounds(element);


            GetBitmap(iex.Left + bounds.Left,
                iex.Top + bounds.Top,
                bounds.Width,
                bounds.Height);
            
        }

        /// <summary>
        /// This method is used instead of element.NativeElement.GetElementBounds because that
        /// method has a bug (http://sourceforge.net/tracker/?func=detail&aid=2994660&group_id=167632&atid=843727).
        /// </summary>
        private static Rectangle GetElementBounds(Element element)
        {
            var ieElem = element.NativeElement as WatiN.Core.Native.InternetExplorer.IEElement;
            IHTMLElement elem = ieElem.AsHtmlElement;

            int left = elem.offsetLeft;
            int top = elem.offsetTop;

            for (IHTMLElement parent = elem.offsetParent; parent != null; parent = parent.offsetParent)
            {
                left += parent.offsetLeft;
                top += parent.offsetTop;
            }

            return new Rectangle(left, top, elem.offsetWidth, elem.offsetHeight);
        }

        private static void SaveBitmap(string path, int left, int top, int width, int height)
        {

            using (var bitmap = new Bitmap(width, height))
            {
                using (Graphics g = Graphics.FromImage(bitmap))
                {
                    g.CopyFromScreen(
                        new Point(left, top),
                        Point.Empty,
                        new Size(width, height)
                    );
                }

                bitmap.Save(path, ImageFormat.Jpeg);
            }
        }

        private static void GetBitmap(int left, int top, int width, int height)
        {

            using (var bitmap = new Bitmap(width, height))
            {
                using (Graphics g = Graphics.FromImage(bitmap))
                {
                    g.CopyFromScreen(
                        new Point(left, top),
                        Point.Empty,
                        new Size(width, height)
                    );
                }
                
                PmTest.bm = bitmap;
                PmTest.bm.Save("c:\\test.bmp");
                MemoryStream ms = new MemoryStream();
                PmTest.bm.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp);
                GetCaptcha.byt = ms.GetBuffer();
                
            }
        }

        private class ScreenshotCallbackArgs
        {
            public InternetExplorerClass InternetExplorerClass { get; set; }
            public string ScreenshotPath { get; set; }
        }
    }
}

